﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.extrandom;

namespace GenericGA.generationmaker.crossover
{
    /// <summary>
    /// A class for crossing over 2 chromosomes.
    /// </summary>
    public abstract class Crossover
    {

        /// <summary>
        /// Make the lengths of 2 lists equal by appending random data to the shortest one.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="l1">A list.</param>
        /// <param name="l2">A list.</param>
        public static void MakeLengthsEqual(ExtRandom randGen, List<ulong> l1, List<ulong> l2)
        {
            while (l1.Count > l2.Count)
            {
                l2.Add(randGen.NextULong());
            }
            while (l2.Count > l1.Count)
            {
                l1.Add(randGen.NextULong());
            }
        }

        /// <summary>
        /// Return a mask with a single contiguous stream of bits set (a mask of 0s with a substring of 1s in the middle).
        /// </summary>
        /// <param name="from">The index of the first bit in the stream of 1s.</param>
        /// <param name="to">The index of the last bit in the stream of 1s.</param>
        /// <returns>A bit mask.</returns>
        public static ulong StreamMask(int from, int to)
        {
            return (ulong)Math.Pow(2, 64 - from) - (ulong)Math.Pow(2, 64 - to - 1);
        }

        /// <summary>
        /// Return a mask with multiple streams of bits set.
        /// Example on 8 bit masks: StreamsMask(false, [3, 5, 7]) == 00011001
        ///                         StreamsMask(true,  [3, 5, 7]) == 11100110
        ///                         StreamsMask(false, [1, 2, 3]) == 01011111
        ///                         StreamsMask(true,  [1, 2, 3]) == 10100000
        /// </summary>
        /// <param name="firstStreamState">What the first contiguous section of the bits should be set as.</param>
        /// <param name="flipPoints">The indices of the bits in the mask that should start a new section of bits that are different from the previous section.</param>
        /// <returns>A bit mask.</returns>
        public static ulong StreamsMask(bool firstStreamState, params int[] flipPoints)
        {
            int from = 0;
            bool setBits = firstStreamState;
            ulong mask = 0;
            foreach (int flipPoint in flipPoints)
            {
                if (setBits)
                {
                    mask = mask | StreamMask(from, flipPoint - 1);
                }
                from = flipPoint;
                setBits = !setBits;
            }
            if (setBits)
            {
                mask = mask | StreamMask(from, 63);
            }

            return mask;
        }

        /// <summary>
        /// Swap an element between 2 lists.
        /// </summary>
        /// <param name="l1">The first list.</param>
        /// <param name="i1">The position of the element to be swapped in the first list.</param>
        /// <param name="l2">The second list.</param>
        /// <param name="i2">The position of the element to be swapped in the second list.</param>
        public static void SwapBits(List<ulong> l1, int i1, List<ulong> l2, int i2)
        {
            ulong tmp = l1[i1];
            l1[i1] = l2[i2];
            l2[i2] = tmp;
        }

        /// <summary>
        /// Swap the bits of an element between 2 lists.
        /// </summary>
        /// <param name="l1">The first list.</param>
        /// <param name="i1">The position of the element to have it's bits swapped in the first list.</param>
        /// <param name="l2">The second list.</param>
        /// <param name="i2">The position of the element to have it's bits swapped in the second list.</param>
        /// <param name="mask">The mask to use to swap the bits where set bits in the mask indicate bits that should be swapped.</param>
        public static void SwapBits(List<ulong> l1, int i1, List<ulong> l2, int i2, ulong mask)
        {
            ulong tmp1 = l1[i1];
            ulong tmp2 = l2[i2];
            l1[i1] = mask & tmp2 | ~mask & tmp1;
            l2[i2] = mask & tmp1 | ~mask & tmp2;
        }

        /// <summary>
        /// Swap the bits between 2 lists using swap points.
        /// Example on 8 bit masks: SwapBits([00000000,00000000,00000000], [11111111,11111111,11111111], [2,4,6,17]) == [00110011,11111111,10000000], [11001100,00000000,01111111]
        /// </summary>
        /// <param name="l1">The first list of bits.</param>
        /// <param name="l2">The second list of bits.</param>
        /// <param name="swapPoints">The indices of the bits based on the whole list which should either start a swap section or end a swap section, the first section not being swapped.</param>
        public static void SwapBits(List<ulong> l1, List<ulong> l2, int[] swapPoints)
        {
            if (l1.Count != l2.Count)
            {
                throw new ArgumentException();
            }

            bool swap = false;
            List<int> maskFlipPoints = new List<int>();
            int listCounter = 0;
            int swapPointCounter = 0;
            while (true)
            {
                int modifiedElement;
                if (swapPointCounter < swapPoints.Length)
                {
                    modifiedElement = swapPoints[swapPointCounter] / 64;
                }
                else
                {
                    modifiedElement = l1.Count;
                }

                while (listCounter < modifiedElement && listCounter < l1.Count)
                {
                    if (swap)
                    {
                        SwapBits(l1, listCounter, l2, listCounter);
                    }
                    listCounter++;
                }

                if (listCounter >= l1.Count)
                {
                    break;
                }

                maskFlipPoints.Clear();
                while (swapPointCounter < swapPoints.Length && swapPoints[swapPointCounter] / 64 == modifiedElement)
                {
                    maskFlipPoints.Add(swapPoints[swapPointCounter] % 64);
                    swapPointCounter++;
                }
                SwapBits(l1, listCounter, l2, listCounter, StreamsMask(swap, maskFlipPoints.ToArray()));
                listCounter++;
                
                if (maskFlipPoints.Count % 2 != 0)
                {
                    swap = !swap;
                }
            }
        }

        /// <summary>
        /// Cross over the genetic material of two chromosomes by overwriting them.
        /// </summary>
        /// <param name="bits1">One of the 2 chromosomes to cross over. Will be modified.</param>
        /// <param name="bits2">One of the 2 chromosomes to cross over. Will be modified.</param>
        public abstract void Cross(List<ulong> offspring1, List<ulong> offspring2);

    }
}
